# -*- coding: utf-8 -*-

__author__ = 'sysprog'

from common import logable, trace_method
from common import NDW_DEBUG


@logable
class ControlInfoObject(object):
    def __init__(self):
        self.rulesets = []
        self.usage_map = {}

    @trace_method
    def add_signature(self, signature_dict):
        __ascii = lambda string: string.decode("ascii")
        frs = FilterRuleSet()

        if u"name" in signature_dict:
            frs.set_by_name_rule(__ascii(signature_dict[u"name"]))

        if u"type" in signature_dict:
            frs.set_by_type_rule(__ascii(signature_dict[u"type"]))

        if u"cann_type" in signature_dict:
            frs.set_by_cann_type_rule(__ascii(signature_dict[u"cann_type"]))

        if u"byte_size" in signature_dict:
            frs.set_by_byte_size_rule(signature_dict[u"byte_size"])

        if u"is_global" in signature_dict:
            frs.set_is_global(signature_dict[u"is_global"])

        if u"logic" in signature_dict:
            frs.set_logic(__ascii(signature_dict[u"logic"]))

        self.rulesets.append(frs)

    @trace_method
    def load_signatures(self, signarute_file_name):
        import ConfigParser
        config = ConfigParser.ConfigParser()
        config.read(signarute_file_name)

        for ruleset in config.sections():
            frs = FilterRuleSet()

            if config.has_option(ruleset, "name"):
                nm = config.get(ruleset, "name")
                frs.set_by_name_rule(nm)

            if config.has_option(ruleset, "type"):
                tp = config.get(ruleset, "type")
                frs.set_by_type_rule(tp)

            if config.has_option(ruleset, "cann_type"):
                ctp = config.get(ruleset, "cann_type")
                frs.set_by_cann_type_rule(ctp)

            if config.has_option(ruleset, "byte_size"):
                bs = config.getint(ruleset, "byte_size")
                frs.set_by_byte_size_rule(bs)

            if config.has_option(ruleset, "is_global"):
                ig = config.getboolean(ruleset, "is_global")
                frs.set_is_global(ig)

            self.rulesets.append(frs)

    @trace_method
    def load_from_tu(self, tu):
        import clang.cindex as ci
        from utils.common import walk_ast_tree

        def func_def_visitor(node, parent, **kwargs):
            if node.kind == ci.CursorKind.FUNCTION_DECL:
                usage_map = kwargs["usage_map"]
                used_in = node

                walk_ast_tree(node, node, var_usage_callback, used_in=used_in, usage_map=usage_map)
                return 1

            return 0

        def var_usage_callback(node, parent, **kwargs):
            if node.kind == ci.CursorKind.DECL_REF_EXPR:
                dfn = node.get_definition()
                if dfn is None:
                    # this is item from std lib
                    self.log(NDW_DEBUG, "stdlib member '%s'", node.displayname)
                    return 1
                if dfn.kind == ci.CursorKind.VAR_DECL:
                    used_in = kwargs["used_in"]
                    usage_map = kwargs["usage_map"]
                    var_usr = dfn.get_usr()

                    var = VarSignature(dfn)
                    is_accept_by_filter = reduce(lambda x, y: x or y, map(lambda x: x.map(var), self.rulesets))

                    if is_accept_by_filter:
                        if var_usr not in usage_map:
                            usage_map[var_usr] = {"var": dfn, "used_in": []}
                        if used_in not in usage_map[var_usr]["used_in"]:
                            usage_map[var_usr]["used_in"].append(used_in)
            return 0

        self.usage_map = {}
        walk_ast_tree(tu.cursor, tu.cursor, func_def_visitor, usage_map=self.usage_map)
        for var in self.usage_map:
            self.usage_map[var]["used_in_list"] = ", ".join( "%s %s" % (it.result_type.spelling,it.displayname) for it in self.usage_map[var]["used_in"])
        self.log(NDW_DEBUG, "usage_map = %s", self.usage_map)


class VarSignature(object):
    def __init__(self, var):
        import clang.cindex as ci
        __is_var_global = lambda var: var.semantic_parent.kind == ci.CursorKind.TRANSLATION_UNIT

        self.byte_size = var.type.get_align()
        self.name = var.spelling
        self.type = var.type.spelling
        self.cann_type = var.get_definition().type.get_canonical().spelling
        self.is_global_var = __is_var_global(var)
        self.usr = var.get_usr()

    def __str__(self):
        fmt = "{name='%(name)s' type='%(type)s' cann_type='%(cann_type)s' byte_size='%(byte_size)d' is_global='%(is_global)s'}"
        fmt_dict = {"name": self.name, "type": self.type, "cann_type": self.cann_type, "byte_size": self.byte_size,
                    "is_global": self.is_global_var}
        return fmt % fmt_dict


@logable
class FilterRuleSet(object):
    def __init__(self):
        self.__DEBUG = True
        self.rules_map = {"by_name": None, "by_type": None, "by_bytesize": None,
                           "by_cann_type": None, "is_global": None}

        # by default = 'OR' logic
        self.logic = lambda r1_res, r2_res: r1_res or r2_res
        self.__str_fmt = []
    """
        All rules receive VarSignature and return boolean. For example
            assert(type(some_int_var) is VarSignature)
            frs.rules_map["by_type"](some_int_var) == True
    """
    def set_by_name_rule(self, pattern):
        import re
        self.rules_map["by_name"] = lambda var: None != re.match(pattern, var.name)
        self.__str_fmt.append("name='%s'" % pattern.strip("\""))

    def set_by_type_rule(self, expected_type):
        self.rules_map["by_type"] = lambda var: var.type == expected_type
        self.__str_fmt.append("type='%s'" % expected_type.strip("\""))

    def set_by_byte_size_rule(self, expected_byte):
        self.rules_map["by_bytesize"] = lambda var: var.byte_size == expected_byte
        self.__str_fmt.append("byte_size=%s" % expected_byte)

    def set_by_cann_type_rule(self, expected_cann_type):
        self.rules_map["by_cann_type"] = lambda var: var.cann_type == expected_cann_type
        self.__str_fmt.append("cann_type='%s'" % expected_cann_type.strip("\""))

    def set_is_global(self, expected_is_global):
        self.rules_map["is_global"] = lambda var: var.is_global_var == expected_is_global
        self.__str_fmt.append("is_global=%s" % expected_is_global)

    def set_logic(self, logic):
        if logic == "AND":
            self.logic = lambda r1_res, r2_res: r1_res and r2_res
            self.__str_fmt.append("logic='%s'" % logic)
        elif logic == "OR":
            self.logic = lambda r1_res, r2_res: r1_res or r2_res
            self.__str_fmt.append("logic='%s'" % logic)

    @trace_method
    def map(self, var_signature):
        res_list = []

        if self.rules_map["by_name"]:
            res_list.append(self.rules_map["by_name"](var_signature))

        if self.rules_map["by_type"]:
            res_list.append(self.rules_map["by_type"](var_signature))

        if self.rules_map["by_bytesize"]:
            res_list.append(self.rules_map["by_bytesize"](var_signature))

        if self.rules_map["by_cann_type"]:
            res_list.append(self.rules_map["by_cann_type"](var_signature))

        if self.rules_map["is_global"]:
            res_list.append(self.rules_map["is_global"](var_signature))

        self.log(NDW_DEBUG, "res_list before reduce = %s", res_list)

        return reduce(lambda x,y : self.logic(x,y), res_list)

    def __str__(self):
        self.__str_fmt.sort()
        return ", ".join(self.__str_fmt)
